#include "meshViewer.h"
#include "m3dLoader.h"
#include "textureMgr.h"
#include "mathHelper.h"

extern HRESULT CompileShaderFromFile(WCHAR* szFileName, LPCSTR szEntryPoint, LPCSTR szShaderModel, ID3DBlob** ppBlobOut);

CMeshViewer::~CMeshViewer()
{
	if (md3dImmediateContext) md3dImmediateContext->ClearState();

	if (mModel)
	{
		delete mModel;
	}
	if (mSampleLinear)
	{
		mSampleLinear->Release();
	}
	if (mWireframeRS)
	{
		mWireframeRS->Release();
	}
	if (mCBViewProj) mCBViewProj->Release();
	if (mInputLayout) mInputLayout->Release();

	if (mVS) mVS->Release();
	if (mPS) mPS->Release();


}
bool CMeshViewer::Init()
{

	if (!D3DApp::Init())
		return false;

	// Compile the vertex shader
	ID3DBlob* pVSBlob = NULL;
	HRESULT hr = CompileShaderFromFile(L"PosVertex.fx", "VS", "vs_4_0", &pVSBlob);
	if (FAILED(hr))
	{
		MessageBox(NULL,
			L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK);
		return hr;
	}

	// Compile the pixel shader
	ID3DBlob* pPSBlob = NULL;
	hr = CompileShaderFromFile(L"PosVertex.fx", "PS", "ps_4_0", &pPSBlob);
	if (FAILED(hr))
	{
		MessageBox(NULL,
			L"The FX file cannot be compiled.  Please run this executable from the directory that contains the FX file.", L"Error", MB_OK);
		return hr;
	}

	// Create the vertex shader
	hr = md3dDevice->CreateVertexShader(pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &mVS);
	if (FAILED(hr))
	{
		pVSBlob->Release();
		return hr;
	}

	// Create the pixel shader
	hr = md3dDevice->CreatePixelShader(pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &mPS);
	pPSBlob->Release();
	if (FAILED(hr))
		return hr;


	// Define the input layout
	D3D11_INPUT_ELEMENT_DESC layout[] =
	{
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "WEIGHTS", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 44, D3D11_INPUT_PER_VERTEX_DATA, 0 },
		{ "BONEINDICES", 0, DXGI_FORMAT_R8G8B8A8_UINT, 0, 56, D3D11_INPUT_PER_VERTEX_DATA, 0 }
	};
	UINT numElements = ARRAYSIZE(layout);


	// Create the input layout
	hr = md3dDevice->CreateInputLayout(layout, numElements, pVSBlob->GetBufferPointer(),
		pVSBlob->GetBufferSize(), &mInputLayout);
	pVSBlob->Release();
	if (FAILED(hr))
		return hr;

	//load vertex and index data
	std::string filename = "soldier.m3d";
	std::wstring texturePath = L"";
	
	mModel = new BaseModel(md3dDevice, *TextureMgr::Instance(), filename, texturePath);

	// Create the constant buffer
	D3D11_BUFFER_DESC bd;
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(CBViewProj);
	bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bd.CPUAccessFlags = 0;
	bd.MiscFlags = 0;
	hr = md3dDevice->CreateBuffer(&bd, NULL, &mCBViewProj);
	if (FAILED(hr))
		return hr;

	//
	bd.Usage = D3D11_USAGE_DEFAULT;
	bd.ByteWidth = sizeof(CBPerFrame);
	bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	bd.CPUAccessFlags = 0;
	bd.MiscFlags = 0;
	hr = md3dDevice->CreateBuffer(&bd, NULL, &mCBPerFrame);
	if (FAILED(hr))
	{
		return hr;
	}

	float x = 0.0f;
	float y = 50.0f;
	float z = -50.0f;
	// Initialize the view matrix
	XMVECTOR Eye = XMVectorSet(x, y, z, 0.0f);
	XMVECTOR At = XMVectorSet(0.0f, 0, 0.0f, 0.0f);
	XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	mCam.LookAt(Eye, At, Up);
	mCam.SetFrustum(XM_PIDIV2, mClientWidth / (FLOAT)mClientHeight, 0.01f, 1000.0f);
	mCam.UpdateViewMatrix();

	//
	// Update constantbuffer
	//
	CBViewProj cb;
	cb.mView = XMMatrixTranspose(mCam.GetViewMatrix());
	cb.mProj = XMMatrixTranspose(mCam.GetProjMatrix());
	md3dImmediateContext->UpdateSubresource(mCBViewProj, 0, NULL, &cb, 0, 0);


	// Directional light.
	mDirectionalLight.Ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
	mDirectionalLight.Diffuse = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
	mDirectionalLight.Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
	mDirectionalLight.Direction = XMFLOAT3(0.57735f, -0.57735f, 0.57735f);
	
	//create constant buffer
	CBPerFrame cbPerFrame;
	cbPerFrame.mDirectionalLight = mDirectionalLight;
	cbPerFrame.mEyePos = XMFLOAT3(x, y, z);
	cbPerFrame.pad = 0.0f;
	md3dImmediateContext->UpdateSubresource(mCBPerFrame, 0, NULL, &cbPerFrame, 0, 0);

	//create rasterizerstate
	D3D11_RASTERIZER_DESC rsdc;
	rsdc.FillMode = D3D11_FILL_SOLID;
	rsdc.CullMode = D3D11_CULL_NONE;
	rsdc.FrontCounterClockwise = FALSE;
	rsdc.DepthBias = 0;
	rsdc.DepthBiasClamp = 0;
	rsdc.SlopeScaledDepthBias = 0;
	rsdc.MultisampleEnable = FALSE;
	rsdc.DepthClipEnable = TRUE;
	rsdc.AntialiasedLineEnable = FALSE;
	rsdc.ScissorEnable = FALSE;

	hr = md3dDevice->CreateRasterizerState(&rsdc, &mWireframeRS);
	if (FAILED(hr))
	{
		return false;
	}

	// Create the sample state
	D3D11_SAMPLER_DESC sampDesc;
	ZeroMemory(&sampDesc, sizeof(sampDesc));
	sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	sampDesc.MinLOD = 0;
	sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
	hr = md3dDevice->CreateSamplerState(&sampDesc, &mSampleLinear);
	if (FAILED(hr))
		return hr;

	mClipName = mModel->mClipName;
	mAnimTimePos = mModel->mSkinnedData.GetClipStartTime(mClipName);
	return true;
}

void CMeshViewer::Update(float dt)
{
	//
	// Control the camera.
	//
	if (GetAsyncKeyState('W') & 0x8000)
		mCam.Walk(10.0f*dt);

	if (GetAsyncKeyState('S') & 0x8000)
		mCam.Walk(-10.0f*dt);

	if (GetAsyncKeyState('A') & 0x8000)
		mCam.Strafe(-10.0f*dt);

	if (GetAsyncKeyState('D') & 0x8000)
		mCam.Strafe(10.0f*dt);

	if (GetAsyncKeyState('Q') & 0x8000)
		mCam.Updown(-10.0f*dt);

	if (GetAsyncKeyState('E') & 0x8000)
		mCam.Updown(10.0f*dt);

	mCam.UpdateViewMatrix();

	mAnimTimePos += dt;
	if (mAnimTimePos >= mModel->mSkinnedData.GetClipEndTime(mClipName))
	{
		// Loop animation back to beginning.
		mAnimTimePos = mModel->mSkinnedData.GetClipStartTime(mClipName);
	}
	mModel->Update(mAnimTimePos);
}

void CMeshViewer::DrawScene()
{
	assert(md3dImmediateContext);
	assert(mSwapChain);
	//
	// Clear the backbuffer
	//
	float ClearColor[4] = { 0.0f, 0.125f, 0.6f, 1.0f }; // RGBA

	// Clear the back buffer blue. Colors::Blue is defined in d3dUtil.h.
	md3dImmediateContext->ClearRenderTargetView(mRenderTargetView,
		ClearColor);
	// Clear the depth buffer to 1.0f and the stencil buffer to 0.
	md3dImmediateContext->ClearDepthStencilView(mDepthStencilView,
		D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

	CBViewProj cb;
	cb.mView = XMMatrixTranspose(mCam.GetViewMatrix());
	cb.mProj = XMMatrixTranspose(mCam.GetProjMatrix());
	md3dImmediateContext->UpdateSubresource(mCBViewProj, 0, NULL, &cb, 0, 0);

	// Set the input layout
	md3dImmediateContext->IASetInputLayout(mInputLayout);

	// Set primitive topology
	md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	md3dImmediateContext->VSSetShader(mVS, NULL, 0);
	md3dImmediateContext->VSSetConstantBuffers(0, 1, &mCBViewProj);
	md3dImmediateContext->PSSetShader(mPS, NULL, 0);
	md3dImmediateContext->PSSetConstantBuffers(1, 1, &mCBPerFrame);
	md3dImmediateContext->PSSetSamplers(0, 1, &mSampleLinear);

	md3dImmediateContext->RSSetState(mWireframeRS);

	//md3dImmediateContext->DrawIndexed(mNumIndex, 0, 0);
	mModel->Draw(md3dImmediateContext);

	// Present the back buffer to the screen.
	HR(mSwapChain->Present(0, 0));
}

void CMeshViewer::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;

	SetCapture(mhMainWnd);
}

void CMeshViewer::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

void CMeshViewer::OnMouseMove(WPARAM btnState, int x, int y)
{
	if ((btnState & MK_LBUTTON) != 0)
	{
		// Make each pixel correspond to a quarter of a degree.
		float dx = XMConvertToRadians(0.25f*static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f*static_cast<float>(y - mLastMousePos.y));

		mCam.Pitch(dy);
		mCam.RotateY(dx);
	}

	mLastMousePos.x = x;
	mLastMousePos.y = y;
}